﻿using UnityEngine;

namespace FFTOcean
{
    public class FFTOcean : MonoBehaviour
    {
        ///<summary>
        /// 横纵向顶点个数
        ///</summary>
        public int N = 64;

        ///<summary>
        /// 影响波浪高度
        ///</summary>
        public float A = 0.0002f;

        ///<summary>
        /// 风向
        ///</summary>
        private Vector2 OMEGA = new Vector2(1, 1);

        ///<summary>
        /// 风速
        ///</summary>
        public float V = 32;
        private OceanCalculation m_oceancalculation;

        ///<summary>
        /// 网格顶点的初始水平间距
        ///</summary>
        public float verticeSpaceBetween = 1; //网格顶点的间距

        ///<summary>
        /// 缩放顶点高度
        ///</summary>
        public float heightScale = 1;

        ///<summary>
        /// 缩放时间速度
        ///</summary>
        public float timeSpeed = 0.5f;

        ///<summary>
        /// 缩放水平位移
        ///</summary>
        public float lambda = 1;

        ///<summary>
        /// x轴tiling个数
        ///</summary>
        public int m_grid_x = 2;

        ///<summary>
        /// z轴tiling个数
        ///</summary>
        public int m_grid_z = 2;

        Material m_mat;
        Texture2D m_fresnelLookUp;
        private Texture2D m_foamTex;
        private Complex[,] m_height;
        private Complex[][,] m_displacement;
        private Complex[][,] m_normal;
        private Complex[][,] m_foam;
        private Mesh m_mesh;
        private int Nplus1;
        private GameObject[] m_ocean_grid;

        void Start()
        {
            m_oceancalculation = new OceanCalculation(N, A, OMEGA, V);

            Nplus1 = N + 1;
            m_height = new Complex[Nplus1, Nplus1];
            m_displacement = new Complex[2][,];
            m_normal = new Complex[2][,];
            m_foam = new Complex[3][,];

            m_mat = this.GetComponent<Renderer>().material;
            m_foamTex = new Texture2D(Nplus1, Nplus1);
            m_mesh = new Mesh();
            m_ocean_grid = new GameObject[m_grid_x * m_grid_z];

            int num = 2;
            for (int i = 0; i < num; i++)
            {
                m_displacement[i] = new Complex[Nplus1, Nplus1];
                m_normal[i] = new Complex[Nplus1, Nplus1];
            }
            for (int i = 0; i < 3; i++)
            {
                m_foam[i] = new Complex[Nplus1, Nplus1];
            }


            Vector2[] uvs = new Vector2[Nplus1 * Nplus1];
            float nplus1 = Nplus1;
            for (int i = 0; i < Nplus1; i++)
            {
                for (int j = 0; j < Nplus1; j++)
                {
                    int index = i * Nplus1 + j;
                    uvs[index].x = i / nplus1;
                    uvs[index].y = j / nplus1;
                }
            }
            Vector3[] newVertices = new Vector3[Nplus1 * Nplus1];
            m_mesh.SetVertices(newVertices);
            m_mesh.SetUVs(0, uvs);


            float n = N;
            for (int x = 0; x < m_grid_x; x++)
            {
                for (int z = 0; z < m_grid_z; z++)
                {
                    int idx = x + z * m_grid_x;

                    m_ocean_grid[idx] = new GameObject("Ocean grid " + idx.ToString());
                    m_ocean_grid[idx].AddComponent<MeshFilter>();
                    m_ocean_grid[idx].AddComponent<MeshRenderer>();
                    m_ocean_grid[idx].GetComponent<Renderer>().material = m_mat;
                    m_ocean_grid[idx].GetComponent<MeshFilter>().mesh = m_mesh;
                    m_ocean_grid[idx].transform.Translate(new Vector3((x * n - m_grid_x * n / 2) * verticeSpaceBetween, 0.0f, (z * n - m_grid_z * n / 2)) * verticeSpaceBetween);
                    m_ocean_grid[idx].transform.parent = this.transform;
                }
            }
        }

        void Update()
        {
            float t = Time.realtimeSinceStartup * timeSpeed;
            m_oceancalculation.FFTCalculation(m_height, m_displacement, m_normal, m_foam, t);

            // 计算法线
            Vector3[] newNormals = new Vector3[Nplus1 * Nplus1];
            Vector3[] newVertices = new Vector3[Nplus1 * Nplus1];


            Color c = new Color(0, 0, 0, 0);
            // 计算顶点坐标
            int sign;
            float[] signs = new float[] { 1.0f, -1.0f };

            for (int i = 0; i < Nplus1; i++)
            {
                for (int j = 0; j < Nplus1; j++)
                {
                    sign = (int)signs[(i + j) & 1];

                    int index = i * Nplus1 + j;

                    // 计算顶点位置
                    float x = (i) * verticeSpaceBetween + lambda * m_displacement[0][i, j].Real * sign;
                    float z = (j) * verticeSpaceBetween + lambda * m_displacement[1][i, j].Real * sign;
                    float y = m_height[i, j].Real * heightScale * sign;
                    newVertices[index] = new Vector3(x, y, z);

                    // 计算法线
                    newNormals[index].x = (-1) * m_normal[0][i, j].Real * sign; //TODO 法线计算是取了梯度的实部
                    newNormals[index].z = (-1) * m_normal[1][i, j].Real * sign;
                    newNormals[index].y = 1f;
                    newNormals[index].Normalize();

                    // 计算白沫判定纹理,产生白沫的位置置1
                    Complex Jxx = 1f + lambda * m_foam[0][i, j] * sign;
                    Complex Jxz = lambda * m_foam[1][i, j];
                    Complex Jzz = 1f + lambda * m_foam[2][i, j] * sign;
                    Complex Jaccobi = Jxx * Jzz - Jxz * Jxz;
                    c.r = Jaccobi.Real < 0 ? 1 : 0;
                    m_foamTex.SetPixel(i, j, c);
                }
            }
            m_foamTex.Apply();

            int[] newTriangles = new int[N * N * 6];
            int num = 0;
            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < N; j++)
                {
                    newTriangles[num++] = i + j * Nplus1;
                    newTriangles[num++] = i + (j + 1) * Nplus1;
                    newTriangles[num++] = (i + 1) + j * Nplus1;

                    newTriangles[num++] = i + (j + 1) * Nplus1;
                    newTriangles[num++] = (i + 1) + (j + 1) * Nplus1;
                    newTriangles[num++] = (i + 1) + j * Nplus1;
                }
            }

            m_mesh.SetVertices(newVertices);
            m_mesh.SetTriangles(newTriangles, 0);
            m_mesh.SetNormals(newNormals);
            m_mesh.RecalculateBounds();

            CreateFresnelLookUp();
            m_mat.SetTexture("_UseFoamTex", m_foamTex);
        }
        void CreateFresnelLookUp()
        {
            float nSnell = 1.34f; //Refractive index of water

            m_fresnelLookUp = new Texture2D(512, 1, TextureFormat.Alpha8, false);
            m_fresnelLookUp.filterMode = FilterMode.Bilinear;
            m_fresnelLookUp.wrapMode = TextureWrapMode.Clamp;
            m_fresnelLookUp.anisoLevel = 0;

            for (int x = 0; x < 512; x++)
            {
                float fresnel = 0.0f;
                float costhetai = (float)x / 511.0f;
                float thetai = Mathf.Acos(costhetai);
                float sinthetat = Mathf.Sin(thetai) / nSnell;
                float thetat = Mathf.Asin(sinthetat);

                if (thetai == 0.0f)
                {
                    fresnel = (nSnell - 1.0f) / (nSnell + 1.0f);
                    fresnel = fresnel * fresnel;
                }
                else
                {
                    float fs = Mathf.Sin(thetat - thetai) / Mathf.Sin(thetat + thetai);
                    float ts = Mathf.Tan(thetat - thetai) / Mathf.Tan(thetat + thetai);
                    fresnel = 0.5f * (fs * fs + ts * ts);
                }

                m_fresnelLookUp.SetPixel(x, 0, new Color(fresnel, fresnel, fresnel, fresnel));
            }

            m_fresnelLookUp.Apply();

            m_mat.SetTexture("_FresnelLookUp", m_fresnelLookUp);
        }
    }
}
